﻿// <copyright file="TimedText.cs" company="Microsoft Corporation">
// ===============================================================================
// MICROSOFT CONFIDENTIAL
// Microsoft Accessibility Business Unit
// Incubation Lab
// Project: Timed Text Library
// ===============================================================================
// Copyright 2009  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
// ===============================================================================
// </copyright>
#region Using
using System;
using System.Collections.Generic;
using System.Xml;
using System.Reflection;
using System.Text.RegularExpressions;
using TimedText.Styling;
using TimedText.Formatting;
using TimedText.Timing;
using System.Text;
using System.Xml.Linq;
using System.Linq;
using TimedText.Parameter;
using System.Globalization;
using System.Runtime.Serialization;
#endregion

namespace TimedText
{
    [Serializable] 
    public class TimedTextException : Exception
    {
        public TimedTextException(string what) : base(what) { }
        public TimedTextException(string what, Exception except) : base(what, except) { }
        public TimedTextException() : base("generic timed text error") { }
        protected TimedTextException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
    } 

    #region Timed Text Attribute
    /// <summary>
    /// Representation of Timed Text attribute data.
    /// </summary>
    public class TimedTextAttributeBase
    {
        #region Private Variables
        private TimedTextElementBase m_parent;
        private string m_localName;
        private string m_namespaceName;
        private string m_value;
        #endregion
 
        #region Properties
        /// <summary>
        /// Element whic owns this attribute
        /// </summary>
        public TimedTextElementBase Parent
        {
            get { return m_parent; }
            set { m_parent = value; }
        }
        /// <summary>
        /// Name of the attribute without namespace
        /// </summary>
        public string LocalName
        {
            get { return m_localName; }
            set { m_localName = value; }
        }
        /// <summary>
        /// Namespace of the attribute
        /// </summary>
        public string NamespaceName
        {
            get { return m_namespaceName; }
            set { m_namespaceName = value; }
        }
        /// <summary>
        /// Value of the attribute
        /// </summary>
        public string Value
        {
            get { return m_value; }
            set { m_value = value; }
        }

        #endregion

        #region Default values
        /// <summary>
        /// Return the Initial value for attributes as defined in the 
        /// timed text specification.
        /// </summary>
        /// <param name="property">Name of the property to look up</param>
        /// <returns>property value</returns>        
        public static object GetInitialStyle(string property)
        {
            switch (property)
            {
                case "backgroundColor": return ColorExpression.Parse("transparent");
                case "color": return ColorExpression.Parse("black");  // spec says transparent
                case "direction": return "auto";  // this is not what the spec says, but we need it to respect writingMode.
                case "display": return "auto";
                case "displayAlign": return "before";
                case "dynamicFlow": return "none";
                case "extent": return new TimedText.Styling.AutoExtent();
                case "fontFamily": return "default";
                case "fontSize": return "1c";
                case "fontStyle": return FontStyleAttributeValue.Regular; ;
                case "fontWeight": return FontWeightAttributeValue.Regular;
                case "lineHeight": return new NormalHeight();  // stand in for normal.
                case "opacity": return 1.0;
                case "origin": return new TimedText.Styling.Origin(0, 0);
                case "overflow": return "hidden";
                case "padding": return new Styling.PaddingThickness("0px");
                case "showBackground": return "always";
                case "textAlign": return "start";
                case "textDecoration": return TextDecorationAttributeValue.None;
                case "textOutline": return new Styling.TextOutline("none");
                case "unicodeBidi": return "undefined";
                case "visibility": return "visible";
                case "wrapOption": return "wrap";
                case "writingMode": return "lrtb";
                case "zIndex": return "auto";

                // these are defaults for the xml attributes
                case "space": return "default";
                case "lang": return "en-us";

                case "region": return "";  // this is not a style as such, but we use the same mechanics.

                // the following cases are for internal styles
                case "#preserve": return false;
                default: return "";
            }
        }
        #endregion

        #region Test Namespaces
        /// <summary>
        /// Test whether this attribute is in the Timed Text Parameter 
        /// namespace
        /// </summary>
        /// <returns></returns>
        public bool IsParameterAttribute()
        {
            bool result = false;
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#parameter");
            result |= (m_namespaceName == "http://www.w3.org/ns/ttml#parameter");
            return result;
        }

        /// <summary>
        /// Test whether this attribute is in the Timed Text Parameter namespace
        /// </summary>
        /// <returns></returns>
        public bool IsProfileAttribute()
        {
            bool result = false;
            result |=  (m_namespaceName.StartsWith("http://www.w3.org/2006/10/ttaf1/profile",StringComparison.Ordinal));
            result |= (m_namespaceName.StartsWith("http://www.w3.org/ns/ttml/profile", StringComparison.Ordinal));
            return result;
        }
        
        /// <summary>
        /// Test whether this attribute is in a Timed Text Style namespace
        /// </summary>
        /// <returns></returns>
        public bool IsStyleAttribute()
        {
            bool result = false;
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#style");
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#styling");
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#style-extensions");
            result |= (m_namespaceName == "http://www.w3.org/ns/ttml#styling");
            return result;
        }

        /// <summary>
        /// Test whether this attribute is in a Timed Text Metadata namespace
        /// </summary>
        /// <returns></returns>
        public bool IsMetadataAttribute()
        {
            bool result = false;
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#metadata");
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1#metadata-extensions");
            result |= (m_namespaceName == "http://www.w3.org/ns/ttml#metadata");
            return result;
        }

        /// <summary>
        /// Test whether this attribute is in a Timed Text Featture namespace
        /// </summary>
        /// <returns></returns>
        public bool IsFeatureAttribute()
        {
            bool result = false;
            result |= (m_namespaceName.StartsWith("http://www.w3.org/2006/10/ttaf1/feature", StringComparison.Ordinal));
            result |= (m_namespaceName.StartsWith("http://www.w3.org/ns/ttml/feature/", StringComparison.Ordinal));
            return result;
        }

        /// <summary>
        /// Test whether this attribute is in a Timed Text Extension 
        /// namespace
        /// [deprecated] this is expected to be removed from timed text
        /// </summary>
        /// <returns></returns>
        public bool IsExtensionAttribute()
        {
            bool result = false;
            result |= (m_namespaceName.StartsWith("http://www.w3.org/2006/10/ttaf1/extension", StringComparison.Ordinal));
            result |= (m_namespaceName.StartsWith("http://www.w3.org/ns/ttml/extension/", StringComparison.Ordinal));
            return result;
        }
  
        /// <summary>
        /// Test whether this attribute is in an intrinsic XML attribute
        /// </summary>
        /// <returns></returns>
        public bool IsXmlAttribute()
        {
            return m_namespaceName == "http://www.w3.org/XML/1998/namespace";
        }

        /// <summary>
        /// Test whether this attribute is in any Timed Text namespace
        /// </summary>
        /// <returns></returns>
        public bool IsTimedTextAttribute()
        {
            bool result = false;
            result |= IsMetadataAttribute() || IsStyleAttribute() || IsParameterAttribute();
            result |= (m_namespaceName == "http://www.w3.org/2006/10/ttaf1");
            result |= (m_namespaceName == "http://www.w3.org/ns/ttml");
            return result;
        }
        #endregion
    }
    #endregion

    #region TimedTextElement abstract base class
    /// <summary>
    /// Base class for all timed text elements. 
    /// Handles parsing and validity
    /// </summary>
    public abstract class TimedTextElementBase 
        : Timing.TimeTree<TimedTextElementBase, TimedTextAttributeBase>.TreeType
    {
        #region Properties

        public string Language
        {
            get;
            set;
        }

        public string LocalName
        {
            get;
            set;
        }

        public string Namespace
        {
            get;
            set;
        }
        
        public BodyElement Body
        {
            get;
            set;
        }

        public string Id
        {
            get;
            set;
        }

        public TtElement Root
        {
            get;
            set;
        }

        //private bool m_preserveWhitespace;
        //public bool PreserveWhitespace
        //{
        //    get
        //    {
        //        return m_preserveWhitespace;
        //    }
        //}


        #endregion

        #region Local members
        // local inline styles.
        private Dictionary<string, object> m_styling = new Dictionary<string, object>();
        
        static double defaultZ = 0.001;  // make up our own z ordering.
        #endregion

        #region Error handler
        protected static void Error(string message)
        {
            throw new TimedTextException(message);
            //Console.WriteLine(message);
        }

        #endregion

        #region static tt_element GetElementFromName(string elem)
        /// <summary>
        /// Instantiate one of the TimedText.* classes from a string representing
        /// its name. 
        /// </summary>
        /// <param name="elem">name of the class to instantiate</param>
        /// <returns>descendent of tt_element</returns>
        static TimedTextElementBase GetElementFromName(string elem)
        {
            // Don't know why this isn't a core .NET function, but its not too
            // hard to drill through the classes.
            Assembly a = Assembly.GetExecutingAssembly();
            Type t = a.GetType(elem, false);
            if (t != null)
            {
                ConstructorInfo c = t.GetConstructor(System.Type.EmptyTypes);
                return c.Invoke(null) as TimedTextElementBase;
            }
            else
            {
                Error("No such element (" + elem + ") in namespace");
            }
            return null;
        }
        #endregion

        #region public virtual bool Valid()
        /// <summary>
        /// Test validity of this subtree
        /// </summary>
        /// <returns>true if valid</returns>
        public virtual bool Valid()
        {
            try
            {
                ValidElements();
                ValidAttributes();  // this has to happen 2nd as we move attributes in ValidElements
            }
            catch (TimedTextException ex)
            {
                Error(ex.Message);
                return false;
            }
            return true;
        }
        #endregion

        #region Attic
        /*
        public virtual bool ReferencesRegion(string p)
        {

            if (p == region.DefaultRegionName) return true;

            if (m_styling.ContainsKey("region"))
            {
                List<string> idrefs = (List<string>)(m_styling["region"]);
                return idrefs.Contains(p);
            }


            bool inRegion = false;
            foreach (tt_element c in Children)
            {
                inRegion |= c.ReferencesRegion(p);
            }
            //((tt_element)Parent).OccupiesRegion(p);
            return inRegion;
        }

        public virtual bool OccupiesRegion(string p)
        {

            if (p == region.DefaultRegionName) return true;

            if (m_styling.ContainsKey("region"))
            {
                List<string> idrefs = (List<string>)(m_styling["region"]);
                return idrefs.Contains(p);
            }

            if (Parent != null)
            {
                return ((tt_element)Parent).OccupiesRegion(p);
            }
            return false;
        }
        */
        #endregion

        #region LocalStyles
        /// <summary>
        /// Set a local style on this element
        /// allows modification at runtime.
        /// </summary>
        /// <param name="property"></param>
        /// <param name="value"></param>
        public void SetLocalStyle(string property, object value)
        {
            m_styling[property] = value;
        }
        /// <summary>
        /// Clear local style on this element
        /// </summary>
        /// <param name="property"></param>
        public void ClearLocalStyle(string property)
        {
            m_styling.Remove(property);
        }
        #endregion

        #region public object GetReferentStyle(string property)
        /// <summary>
        /// Look up a style property in any referent style elements.
        /// </summary>
        /// <param name="property"></param>
        public object GetReferentStyle(string property)
        {
            // check for local override, this will always win
            if (m_styling.ContainsKey(property))
            {
                return m_styling[property];
            }

            // find out if we refer to any other styles.
            if (m_styling.ContainsKey("style"))
            {
                List<string> referentStyles = m_styling["style"] as List<string>;

                if (referentStyles == null || referentStyles.Count == 0)
                {
                    return null;
                }
                // recursively check them in reverse order.
                referentStyles.Reverse();
                foreach (string s in referentStyles)
                {
                    if (Root.Styles.ContainsKey(s))
                    {
                        object result = Root.Styles[s].GetReferentStyle(property);
                        if (result != null)
                        {
                            return result;
                        }
                    }
                }
            }

            return null;
        }
        #endregion

        #region public virtual object GetComputedStyle(string property)
        /// <summary>
        /// Get the final computed value for a style property from this element
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public virtual object GetComputedStyle(string propertyName, RegionElement currentRegion)
        {
            object value;

            value = GetReferentStyle(propertyName);

            // to do: special case visibility, as the parent value=hidden trumps the child value.
            if (value == null || (value is Inherit))
            {
                value = GetInheritedStyle(propertyName, currentRegion);
            }
            return value;
        }

        public object GetInheritedStyle(string property, RegionElement currentRegion)
        {
            bool isBodyElement = this is BodyElement;
            bool canInherit = !(isBodyElement) && !(this is RegionElement);
            if (Parent != null && canInherit)
            {
                return ((TimedTextElementBase)Parent).GetComputedStyle(property, currentRegion);
            } if (isBodyElement && currentRegion != null)
            {
                // body needs to inherit from the region it has been parented to
                return currentRegion.GetComputedStyle(property, null);
            }
            else
            {
                /// return initial value.
                return TimedTextAttributeBase.GetInitialStyle(property);

            }
        }
        #endregion

        #region Layout
        /// <summary>
        /// sub classes need to override this.
        /// </summary>
        /// <returns></returns>
        public virtual Formatting.FormattingObject GetFormattingObject(TimeCode tick)
        {
            return null;
        }
        #endregion

        public virtual void WriteElement(XmlWriter writer)
        {
            if (LocalName == null) return;

            writer.WriteStartElement(this.LocalName, this.Namespace);
            WriteAttributes(writer);
            foreach (TimedTextElementBase element in Children)
            {
                element.WriteElement(writer);
            }
            writer.WriteEndElement();
        }

        public void WriteAttributes(XmlWriter writer)
        {
            foreach (var a in Attributes)
            {
                writer.WriteAttributeString(a.LocalName, a.NamespaceName, a.Value);
            }
        }


        #region Attribute Validation
  
        /// <summary>
        /// Validate attribute classes
        /// </summary>
        /// <param name="parameterSet">true if required to validate parameter attributes</param>
        /// <param name="metadataSet">true if required to validate metadata attributes</param>
        /// <param name="styleSet">true if required to validate style attributes</param>
        /// <param name="timingSet">true if required to validate timing attributes</param>
        /// <param name="regionSet">true if required to validate region attributes</param>
        /// <param name="timeContainerSet">true if required to time container parameter attributes</param>
        protected void ValidateAttributes(bool parameterSet, bool metadataSet, bool styleSet, bool timingSet, bool regionSet, bool timeContainerSet)
        {
            bool seenStyleAttributeInSet = false;
            // now check each of the attributes is individually valid
            foreach (TimedTextAttributeBase attribute in Attributes)
            {
                if (attribute.IsXmlAttribute())
                {
                    ValidXmlAttributeValue(attribute);
                }
                else if (attribute.IsMetadataAttribute())
                {
                    if (metadataSet)
                        ValidMetadataAttributeValue(attribute);
                    else
                        Error("invalid style attribute");
                }
                else if (attribute.IsParameterAttribute())
                {
                    if (parameterSet)
                        ValidParameterAttribute(attribute);
                    else
                        Error("invalid style attribute");
                }
                else if (attribute.IsStyleAttribute())
                {
                    bool isSetElement = this is SetElement;
                    if (styleSet)
                    {
                        if (seenStyleAttributeInSet && isSetElement)
                        {
                            Error("set only allows a single style attribute");
                        }
                        if (isSetElement) seenStyleAttributeInSet = true;
                        ValidStyleAttributeValue(attribute);
                    }
                    else if (this is TtElement && attribute.LocalName == "extent")
                        ValidStyleAttributeValue(attribute);
                    else
                        Error("invalid style attribute");
                }
                else if (attribute.IsTimedTextAttribute())
                {
                    switch (attribute.LocalName)
                    {
                        case "id":
                            // this is actually invalid, but we ignore for the time being.
                            break;
                        case "style":
                            ValidStyleReference(attribute);
                            break;
                        #region timingSet
                        case "begin":
                            if (timingSet)
                                Timing["begin"] = TimeExpression.Parse(attribute.Value);
                            else
                                Error("invalid begin attribute");
                            break;
                        case "end":
                            if (timingSet)
                                Timing["end"] = TimeExpression.Parse(attribute.Value);
                            else
                                Error("invalid begin attribute");
                            break;
                        case "dur":
                            if (timingSet)
                                Timing["dur"] = TimeExpression.Parse(attribute.Value);
                            else
                                Error("invalid begin attribute");
                            break;
                        #endregion

                        case "region":
                            if (regionSet)
                            {
                                ValidRegionAttribute(attribute);
                                break;
                            }
                            else
                            {
                                Error("Erroneous region attribute " + attribute.LocalName + " on " + this.ToString());
                            }
                            break;
                        case "timeContainer":
                            if (timeContainerSet)
                            {
                                ValidTimeContainerAttribute(attribute);
                            }
                            else
                            {
                                Error("Erroneous time container attribute " + attribute.LocalName + " on " + this.ToString());
                                break;
                            }
                            break;
                        default:
                            Error("Erroneous tt: namespace attribute " + attribute.LocalName + " on " + this.ToString());
                            break;
                    }
                }
                else
                {
                    //Error("Erroneous attribute " + attribute.localName + " on " + this.ToString());
                }
            }
        }

        /// <summary>
        /// Validate a parameter attribute
        /// </summary>
        /// <param name="attribute"></param>
        protected void ValidParameterAttribute(TimedTextAttributeBase attribute)
        {
            if (!(this is TtElement)) return;

            switch (attribute.LocalName)
            {
                case "cellResolution":
                    Styling.NumberPair.SetCellSize(attribute.Value);
                    break;
                case "clockMode":
                    switch (attribute.Value)
                    {
                        case "local":
                            TimeExpression.CurrentClockMode = ClockMode.Local;
                            break;
                        case "gps":
                            TimeExpression.CurrentClockMode = ClockMode.Gps;
                            break;
                        case "utc":
                            TimeExpression.CurrentClockMode = ClockMode.Utc;
                            break;
                    }
                    break;
                case "frameRate":
                    // TO DO - put some error checking in here!
                    TimeExpression.CurrentFrameRate = Int32.Parse(attribute.Value, CultureInfo.InvariantCulture);
                    break;
                case "frameRateMultiplier":
                    // TO DO - put some error checking in here!
                    string[] parts = attribute.Value.Split(new char[] { ':', ' ' });
                    TimeExpression.CurrentFrameRateNominator = Int32.Parse(parts[0], CultureInfo.InvariantCulture);
                    TimeExpression.CurrentFrameRateDenominator = Math.Max(1, Int32.Parse(parts[1], CultureInfo.InvariantCulture));
                    break;
                case "markerMode":
                    Root.Parameters[attribute.LocalName] = attribute.Value;
                    break;
                case "pixelAspectRatio":
                    Root.Parameters[attribute.LocalName] = attribute.Value;
                    break;
                case "profile":
                    if (Parameter.ParameterElement.Features.Count == 0)
                    { // we have not seen a profile element, OK to proceed
                        ValidProfileAttribute(attribute);                         
                    }
                    break;
                case "smpteMode":
                    switch (attribute.Value)
                    {
                        case "dropNTSC":
                            TimeExpression.CurrentSmpteMode = SmpteMode.DropNtsc;
                            break;
                        case "dropPAL":
                            TimeExpression.CurrentSmpteMode = SmpteMode.DropPal;
                            break;
                        case "nonDrop":
                            TimeExpression.CurrentSmpteMode = SmpteMode.NonDrop;
                            break;
                    }
                    break;
                case "subFrameRate":
                    // TO DO - put some error checking in here!
                    TimeExpression.CurrentSubFrameRate = Int32.Parse(attribute.Value, CultureInfo.InvariantCulture);
                    break;
                case "tickRate":
                    // TO DO - put some error checking in here!
                    TimeExpression.CurrentTickRate = Int32.Parse(attribute.Value, CultureInfo.InvariantCulture);
                    break;
                case "timeBase":
                    switch (attribute.Value)
                    {
                        case "media":
                            TimeExpression.CurrentTimeBase = TimeBase.Media;
                            break;
                        case "smpte":
                            TimeExpression.CurrentTimeBase = TimeBase.Smpte;
                            break;
                        case "clock":
                            TimeExpression.CurrentTimeBase = TimeBase.Clock;
                            break;
                    }
                    break;
                default:
                    Error("Erroneous parameter attribute on  " + this.ToString());
                    break;
            }
        }

        /// <summary>
        /// Validate a profile attribute
        /// </summary>
        /// <param name="attribute"></param>
        protected static void ValidProfileAttribute(TimedTextAttributeBase attribute)
        {
            Uri designator;
            Uri profile = new Uri("http://www.w3.org/2006/10/ttaf1/profile/");
            try
            {
                designator = new Uri(profile, attribute.Value);
                switch (designator.AbsoluteUri)
                {
                    case "http://www.w3.org/2006/10/ttaf1/profile/dfxp-transformation":
                        foreach (var f in Parameter.ParameterElement.TransformProfile)
                        {
                            if (!Parameter.ParameterElement.Features.ContainsKey(f.Key))
                            {   // if local profile has added this, then dont over-ride it
                                Parameter.ParameterElement.Features[f.Key] = f.Value;
                            }
                        }
                        break;
                    case "http://www.w3.org/2006/10/ttaf1/profile/dfxp-presentation":
                        foreach (var f in Parameter.ParameterElement.PresentationProfile)
                        {
                            if (!Parameter.ParameterElement.Features.ContainsKey(f.Key))
                            {   // if local profile has added this, then dont over-ride it
                                Parameter.ParameterElement.Features[f.Key] = f.Value;
                            }
                        }
                        break;
                    default:
                        throw new TimedTextException("unrecognized profile " + designator.AbsoluteUri);
                }
            }
            catch (TimedTextException)
            {
                throw new TimedTextException("unrecognized profile " + attribute.Value);
            }
        }

        /// <summary>
        /// Validate a time container attribute
        /// </summary>
        /// <param name="attribute"></param>
        protected void ValidTimeContainerAttribute(TimedTextAttributeBase attribute)
        {
            switch (attribute.Value)
            {
                case "par":
                    Timing["timeContainer"] = TimeContainer.Par;
                    TimeSemantics = TimeContainer.Par;
                    break;
                case "seq":
                    Timing["timeContainer"] = TimeContainer.Seq;
                    TimeSemantics = TimeContainer.Seq;
                    break;
                default:
                    Error("Erroneous value for timeContainer on " + this.ToString());
                    break;
            }
        }

        /// <summary>
        /// Validate a region attribute
        /// </summary>
        /// <param name="attribute"></param>
        protected void ValidRegionAttribute(TimedTextAttributeBase attribute)
        {
            //// we should do a pattern match to ensure its legal.
            //List<string> idrefs = new List<string>();
            //char[] whitespace = { (char)0x20 };
            //foreach (string s in attribute.value.Split(whitespace))
            //{
            //    // to do - what we want to do here is check its in m_styles; however that wont work for 
            //    // forward references in styling; can we get a spec restriction here?.
            //    idrefs.Add(s.Trim());
            //}

            // strictly speaking region is not a style attribute, but this is convenient.
            m_styling["region"] = attribute.Value;
        }

        /// <summary>
        /// Validate a style reference attribute
        /// </summary>
        /// <param name="attribute"></param>
        protected void ValidStyleReference(TimedTextAttributeBase attribute)
        {
            //  IDREFS allow only a single space between names. Not sure what happens here if there are multiple.
            // we should do a pattern match to ensure its legal.
            List<string> idrefs = new List<string>();
            char[] whitespace = { (char)0x20 };
            foreach (string s in attribute.Value.Split(whitespace))
            {
                // to do - what we want to do here is check its in m_styles; however that wont work for 
                // forward references in styling; can we get a spec restriction here?.
                idrefs.Add(s.Trim());
            }
            // strictly speaking style is not a style attribute, but this is convenient.
            m_styling["style"] = idrefs;
        }
 
        #endregion

        #region regular expressions for attribute values
        private const string s_lengthExpression = @"((\+|\-)?\d+(\.\d+)?(px|em|c|\%))";
        //private const string s_colorExpression = @"red";   // to do proper regex for color
        private const string s_fontSizeExpression = @"^" + s_lengthExpression + "( +" + s_lengthExpression + ")?$";
        private const string s_paddingExpression = @"^" + s_lengthExpression + "( +" + s_lengthExpression + "( +" + s_lengthExpression + "( +" + s_lengthExpression + ")?)?)?$";
        private const string s_fontStyleExpression = @"^normal|italic|oblique|reverseOblique|inherit$";
        private const string s_fontWeightExpression = @"^normal|bold|inherit$";
        private const string s_directionExpression = "^ltr|rtl|inherit$";
        private const string s_displayExpression = "^auto|none|inherit$";
        private const string s_displayAlignExpression = "^before|center|after|inherit$";
        private const string s_lineHeightExpression = @"^normal|" + s_lengthExpression + "|inherit$";
        private const string s_opacityExpression = @"^(\d+)(\.\d+)?|inherit$";    /// not to spec --- but float is ridiculous.
        private const string s_overflowExpression = @"^visible|hidden|scroll|inherit$";
        private const string s_showBackgroundExpression = @"^always|whenActive|inherit$";
        private const string s_textDecorationExpression = @"^none|underline|noUnderline|lineThrough|noLineThrough|overline|noOverline|inherit$";
        private const string s_writingModeExpression = @"^lrtb|rltb|tbrl|tblr|lr|rl|tb|inherit$";
        private const string s_wrapOptionExpression = @"^wrap|noWrap|inherit$";
        private const string s_visibilityExpression = @"^visible|hidden|inherit$";
        private const string s_unicodeBidiExpression = @"^normal|embed|bidiOverride|inherit$";
        private const string s_zIndexExpression = @"^auto|((\+|\-)?\d+)|inherit$";
        private const string s_textAlignExpression = @"^left|center|right|start|end|inherit$";


        #endregion

        #region Attribute Value Validation
        /// <summary>
        /// Validate an attribute by testing syntax against the given regular expression
        /// </summary>
        /// <param name="matchExpression">Regular expression of syntax</param>
        /// <param name="attribute">Attribute to test</param>
        /// <returns></returns>
        private bool ValidAttributeValue(string matchExpression, TimedTextAttributeBase attribute)
        {
            Regex matchRE = new Regex(matchExpression);
            if (matchRE.IsMatch(attribute.Value.Trim()))
            {
                m_styling[attribute.LocalName] = attribute.Value.Trim();
            }
            else
            {
                Error("Erroneous value " + attribute.Value + " for  attribute " + attribute.LocalName + " on " + this.ToString());
            }
            return true;
        }

        /// <summary>
        /// Validate a style attribute value
        /// </summary>
        /// <param name="attribute">timed text attribute to be validated</param>
        private void ValidStyleAttributeValue(TimedTextAttributeBase attribute)
        {
            if (attribute.Value == "inherit")
            {
                /// to do - remove inherit ias a legal value for attributes.
                m_styling[attribute.LocalName] = new Inherit();
            }
            else
            {
                switch (attribute.LocalName)
                {
                    #region Style Handlers
                    case "backgroundColor":
                        m_styling["backgroundColor"] = ColorExpression.Parse(attribute.Value);
                        break;
                    case "color":
                        m_styling["color"] = ColorExpression.Parse(attribute.Value);
                        break;
                    case "direction":
                        ValidAttributeValue(s_directionExpression, attribute);
                        break;
                    case "display":
                        ValidAttributeValue(s_displayExpression, attribute);
                        break;
                    case "displayAlign":
                        ValidAttributeValue(s_displayAlignExpression, attribute);
                        break;
                    case "dynamicFlow":
                        // Error("dynamicFlow attribute not supported " + this.ToString());
                        break;
                    case "extent":
                        if (attribute.Value.Trim() == "auto")
                        {
                            m_styling["extent"] = new AutoExtent();
                        }
                        else
                        {
                            m_styling["extent"] = new Styling.Extent(attribute.Value);
                        };
                        break;
                    case "fontFamily":
                        string newFontString = attribute.Value
                            .Replace("sansSerif", "Arial")
                            .Replace("serif", "Times New Roman")
                            .Replace("monospaceSansSerif", "Arial")
                            .Replace("monospaceSerif", "Courier New")
                            .Replace("monospace", "Courier New")
                            .Replace("proportionalSansSerif", "GlobalUserInterface.CompositeFont")
                            .Replace("proportionalSerif", "Times New Roman");
                        m_styling["fontFamily"] = newFontString;
                        break;
                    case "fontSize":
                        ValidAttributeValue(s_fontSizeExpression, attribute);
                        break;
                    case "fontStyle":
                        if (ValidAttributeValue(s_fontStyleExpression, attribute))
                        {
                            switch (attribute.Value.Trim())
                            {
                                case "italic": m_styling["fontStyle"] = FontStyleAttributeValue.Italic;
                                    break;
                                case "oblique": m_styling["fontStyle"] = FontStyleAttributeValue.Oblique;
                                    break;
                                case "reverseOblique": m_styling["fontStyle"] = FontStyleAttributeValue.ReverseOblique;
                                    break;
                                default: m_styling["fontStyle"] = FontWeightAttributeValue.Regular;
                                    break;
                            }
                        } break;
                    case "fontWeight":
                        if (ValidAttributeValue(s_fontWeightExpression, attribute))
                        {
                            switch (attribute.Value.Trim())
                            {
                                case "bold": m_styling["fontWeight"] = FontWeightAttributeValue.Bold;
                                    break;
                                default: m_styling["fontWeight"] = FontWeightAttributeValue.Regular;
                                    break;
                            }
                        }
                        break;
                    case "lineHeight":
                        if (ValidAttributeValue(s_lineHeightExpression, attribute))
                        {
                            switch (attribute.Value.Trim())
                            {
                                case "normal":
                                    m_styling["lineHeight"] = new NormalHeight();
                                    break;
                                default: m_styling["lineHeight"] = new LineHeight(attribute.Value);
                                    break;
                            }
                        }
                        break;
                    case "opacity":
                        if (ValidAttributeValue(s_opacityExpression, attribute))
                            m_styling["opacity"] = Double.Parse(attribute.Value, CultureInfo.InvariantCulture);
                        break;
                    case "origin":
                        switch (attribute.Value.Trim())
                        {
                            case "auto":
                                m_styling["origin"] = new AutoOrigin();
                                break;
                            case "inherit":
                                m_styling["origin"] = new Inherit();
                                break;
                            default:
                                m_styling["origin"] = new Styling.Origin(attribute.Value);
                                break;
                        };
                        break;
                    case "overflow":
                        ValidAttributeValue(s_overflowExpression, attribute);
                        break;
                    case "padding":
                        if (ValidAttributeValue(s_paddingExpression, attribute))
                        {
                            m_styling["padding"] = new Styling.PaddingThickness(attribute.Value);
                        }
                        break;
                    case "showBackground":
                        ValidAttributeValue(s_showBackgroundExpression, attribute);
                        break;
                    case "textAlign":
                        ValidAttributeValue(s_textAlignExpression, attribute);
                        break;
                    case "textDecoration":
                        if (ValidAttributeValue(s_textDecorationExpression, attribute))
                        {
                            switch (attribute.Value.Trim())
                            {   //underline | noUnderline ] || [ lineThrough  | noLineThrough ] || [ overline | noOverline
                                case "underline": m_styling["textDecoration"] = TextDecorationAttributeValue.Underline;
                                    break;
                                case "lineThrough": m_styling["textDecoration"] = TextDecorationAttributeValue.Throughline;
                                    break;
                                case "overline": m_styling["textDecoration"] = TextDecorationAttributeValue.Overline;
                                    break;
                                default: m_styling["textDecoration"] = TextDecorationAttributeValue.None;
                                    break;
                            }
                        }
                        break;
                    case "textOutline":
                        m_styling["textOutline"] = new Styling.TextOutline(attribute.Value);
                        break;
                    case "unicodeBidi":
                        ValidAttributeValue(s_unicodeBidiExpression, attribute);
                        break;
                    case "visibility":
                        ValidAttributeValue(s_visibilityExpression, attribute);
                        break;
                    case "wrapOption":
                        ValidAttributeValue(s_wrapOptionExpression, attribute);
                        break;
                    case "writingMode":
                        ValidAttributeValue(s_writingModeExpression, attribute);
                        break;
                    case "zIndex":
                        if (ValidAttributeValue(s_zIndexExpression, attribute))
                        {
                            if (attribute.Value.Trim() == "auto") m_styling["zIndex"] = defaultZ;
                            else m_styling["zIndex"] = ((double)Int32.Parse(attribute.Value, CultureInfo.InvariantCulture)) + defaultZ;
                            defaultZ += 0.0001; // allow for 10,000 regions with the same z order.
                        }
                        break;
                    #endregion
                    default:
                        Error("Erroneous style: namespace attribute " + attribute.LocalName + " on " + this.ToString());
                        break;
                };
            }
        }
 
        /// <summary>
        /// Validate an XML attribute value
        /// </summary>
        /// <param name="attribute"></param>
        protected void ValidXmlAttributeValue(TimedTextAttributeBase attribute)
        {
            switch (attribute.LocalName)
            {
                case "lang":
                    Language = attribute.Value;
                    break;
                case "space":
                    m_styling[attribute.LocalName] = attribute.Value;
                    break;
                case "id":
                    if (this.Id == null)
                    {
                        this.Id = attribute.Value;
                    }
                    else
                    {
                        Error("multiple xml:Id defined on " + this.ToString());
                    }
                    if (this is RegionElement) Root.Regions[attribute.Value] = (RegionElement)this;
                    if (this is StyleElement) Root.Styles[attribute.Value] = (StyleElement)this;
                    if (this is Metadata.AgentElement) Root.Agents[attribute.Value] = (Metadata.AgentElement)this;
                    break;
                default:
                    Error("Erroneous xml: namespace attribute " + attribute.LocalName + " on " + this.ToString());
                    break;
            };
        }
       
        /// <summary>
        /// validate a metadata attribute value
        /// </summary>
        /// <param name="attribute"></param>
        private void ValidMetadataAttributeValue(TimedTextAttributeBase attribute)
        {
            #region metadata attribute

            switch (attribute.LocalName)
            {
                case "agent":
                    // ToDo - ensure its an IDREF to an agent element.
                    break;
                case "role":
                    switch (attribute.Value)
                    {
                        case "action":
                        case "caption":
                        case "description":
                        case "dialog":
                        case "expletive":
                        case "kinesic":
                        case "lyrics":
                        case "music":
                        case "narration":
                        case "quality":
                        case "sound":
                        case "source":
                        case "suppressed":
                        case "reproduction":
                        case "thought":
                        case "title":
                        case "transcription":
                            this.Metadata[attribute.LocalName] = attribute.Value;
                            break;
                        default:
                            if (!attribute.Value.StartsWith("x-", StringComparison.Ordinal))
                            {
                                Error("Erroneous metadata namespace attribute " + attribute.LocalName + " on " + this.ToString());
                            }
                            else
                            {
                                this.Metadata[attribute.LocalName] = attribute.Value;
                            }
                            break;
                    }
                    break;
                default:
                    Error("Erroneous metadata namespace attribute " + attribute.LocalName + " on " + this.ToString());
                    break;
            };
            #endregion
        }
        #endregion

        #region Element Validity
        /// <summary>
        /// Test whether an element's content model, and all its descendants
        /// are valid Timed Text
        /// throws an exception if invalid.
        /// </summary>
        protected abstract void ValidElements();

        /// <summary>
        /// Test whether an elements attributes are allowed by Timed Text
        /// throws an exception if invalid.
        /// </summary>
        protected abstract void ValidAttributes();
        #endregion

        #region Parsing
        /// <summary>
        /// Convert an XElement to the internal TimedText classes.
        /// </summary>
        /// <param name="timedTextData">Raw XML construct</param>
        /// <returns>Timed text Element hierachy</returns>
        public static TimedTextElementBase Parse(XElement timedTextData)
        {
            InitialiseDefaults();
            return ParseRecursive(timedTextData, null, false);
        }

        /// <summary>
        /// Initialise all the components for this parse
        /// </summary>
        private static void InitialiseDefaults()
        {
            TimeExpression.InitializeParameters();
            ParameterElement.InitializeParameters();
        }

        /// <summary>
        /// Convert an XElement to the internal TimedText classes.
        /// </summary>
        /// <param name="timedTextData">Raw XML construct</param>
        /// <param name="root">root element of the tree</param>
        /// <returns>tt_element hierachy</returns>
        private static TimedTextElementBase ParseRecursive(XElement xmlElement, TtElement root, bool preserveContext)
        {
            string element = xmlElement.Name.LocalName;
            string nameSpace = NamespaceFromTimedTextNamespace(xmlElement.Name.NamespaceName);
            TimedTextElementBase parentNode = null;

            if (!string.IsNullOrEmpty(nameSpace))
            {
                // To meet MSFT naming conventions, have to manipulate the name.
                string initialCapital = element[0].ToString().ToUpperInvariant();
                string rest = element.Substring(1);
                string conventionName = initialCapital + rest + "Element";
                // if there is a namespace, then its a timed text element
                parentNode = GetElementFromName(nameSpace + conventionName);
                parentNode.LocalName = element;
                parentNode.Namespace = xmlElement.Name.NamespaceName;
            }
            /// if node is still null, either we failed to implement the element
            /// or its an element in a foreign namespace, either way we bail.
            if (parentNode == null) return null;

            #region test if root element
            TtElement newRoot = (root == null) ? parentNode as TtElement : root;
            // null should only occur in the first call,
            if (newRoot == null)
            {
                Error("tt not at root of document");
            }
            parentNode.Root = newRoot;
            #endregion

            bool localPreserve = preserveContext;  // record whether xml:space=preserve is in effect

            #region process raw xml attributes into timed text equivalents
            foreach (XAttribute xmlAttribute in xmlElement.Attributes())
            {
                if (!xmlAttribute.IsNamespaceDeclaration)
                {
                    // copy the attribute identity
                    TimedTextAttributeBase attribute = new TimedTextAttributeBase();
                    attribute.Parent = parentNode;
                    attribute.LocalName = xmlAttribute.Name.LocalName;
                    attribute.Value = xmlAttribute.Value;
  
                    // not sure if it is absolutely correct to move 
                    // empty namespace elements into tt namespace but seems
                    // to work.
                    attribute.NamespaceName = 
                        (string.IsNullOrEmpty(xmlAttribute.Name.NamespaceName) 
                            ? xmlElement.Name.NamespaceName
                            : xmlAttribute.Name.NamespaceName
                         );

                    // attach new attribute to current element
                    parentNode.Attributes.Add(attribute);

                    // check whether we are changing the space preserve behaviour
                    if (attribute.IsXmlAttribute() && attribute.LocalName == "space")
                    {
                        localPreserve = (attribute.Value == "preserve");
                    }
                    // record the type of preservation as a local style.
                    parentNode.SetLocalStyle("#preserve", localPreserve);
                }
            }
            #endregion

            #region process child elements            
            foreach (XNode xmlNode in xmlElement.Nodes())
            {
                switch (xmlNode.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            #region convert XML Element to Timed Text Element
                            TimedTextElementBase child = ParseRecursive(xmlNode as XElement, newRoot, localPreserve);
                            if (child != null)
                            {
                                parentNode.Children.Add(child);
                                child.Parent = parentNode;
                                child.Root = parentNode.Root;
                            }
                            #endregion
                            break;
                        }
                    case XmlNodeType.Text:
                    case XmlNodeType.CDATA:
                        {
                            #region convert XML Text into an anonymous span element
                            if (IsContentElement(parentNode))
                            { 
                                #region elements that admit PCDATA as children get anonymous spans
                                SpanElement text;
                                if (!localPreserve)
                                {  // squeeze out all the redundant whitespace
                                    string normalised = NormaliseWhitespace(xmlNode);
                                    text = new AnonymousSpanElement(normalised);
                                }
                                else
                                {  // preserve the raw text as it came in
                                    text = new AnonymousSpanElement(xmlNode.ToString());
                                }
                                parentNode.Children.Add(text);
                                text.Parent = parentNode;
                                #endregion
                            }
                            else
                            {
                                #region test non content element for non-whitespace error.
                                if (NormaliseWhitespace(xmlNode) != " ")
                                {
                                    Error("Use of non whitespace in " + parentNode);
                                }
                                #endregion
                            }
                            #endregion
                            break;
                        }
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.Whitespace:
                        {  
                            #region for some reason we dont seem to get these.
                            if (IsContentElement(parentNode))
                            {
                                char[] newlines = { '\n' };// what other newlines are there?
                                foreach (var br in xmlNode.ToString().Split(newlines, StringSplitOptions.None))
                                {
                                    if (string.IsNullOrEmpty(br))
                                    {
                                        parentNode.Children.Add(new BrElement());
                                    }
                                    else
                                    {
                                        SpanElement text = new AnonymousSpanElement(br);
                                        parentNode.Children.Add(text);
                                        text.Parent = parentNode;
                                    }
                                }
                            }
                            #endregion
                            break;
                        }
                }
            }
            #endregion
            return parentNode;
        }
        #endregion
 
        #region Helper Methods

        /// <summary>
        /// convert newlines to space, and collpase runs of space to a single space
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        private static string NormaliseWhitespace(XNode n)
        {
            string normalised = n.ToString().Replace("\n", " ").Replace("\r", " ").Replace("\t", " ");
            while (normalised.Contains("  "))
            {
                normalised = normalised.Replace("  ", " ");
            }
            return normalised;
        }

        /// <summary>
        /// Is it a content element for purposes of parenting anonymous span's?
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static bool IsContentElement(TimedTextElementBase node)
        {
            if (node is PElement) return true;
            if (node is SpanElement) return true;
            if (IsMetadataContentElement(node)) return true;
            if (IsParameterContentElement(node)) return true;
            return false;
        }

        /// <summary>
        /// Metadata items that admit PCDATA as content
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static bool IsMetadataContentElement(TimedTextElementBase node)
        {
            if (node is Metadata.TitleElement) return true;
            if (node is Metadata.NameElement) return true;
            if (node is Metadata.DescElement) return true;
            if (node is Metadata.CopyrightElement) return true;
            if (node is Metadata.AgentElement) return true;
            if (node is Metadata.ActorElement) return true;

            return false;
        }

        /// <summary>
        /// Parameter items that admit PCDATA as content
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static bool IsParameterContentElement(TimedTextElementBase node)
        {
            if (node is Parameter.ExtensionElement) return true;
            if (node is Parameter.FeatureElement) return true; 
            return false;
        }

        #endregion

        #region  Namespace Handling
        /// <summary>
        /// Get the local C# namespace from the Timed Text XML namespace
        /// </summary>
        /// <param name="p">XML namespace</param>
        /// <returns>C# namespace prefix as a string</returns>
        private static string NamespaceFromTimedTextNamespace(string p)
        {
            switch (p)
            {   // got to be a better way to do this using reflection?
                case "http://www.w3.org/2006/10/ttaf1":
                case "http://www.w3.org/ns/ttml":
                    {
                        return "TimedText.";
                    }
                case "http://www.w3.org/2006/10/ttaf1#metadata":
                case "http://www.w3.org/ns/ttml#metadata":
                    {
                        return "TimedText.Metadata.";
                    }
                case "http://www.w3.org/2006/10/ttaf1#style":
                case "http://www.w3.org/2006/10/ttaf1#styling":
                case "http://www.w3.org/ns/ttml#styling":
                    {
                        return "TimedText.Style.";
                    }
                case "http://www.w3.org/2006/10/ttaf1#parameter":
                case "http://www.w3.org/ns/ttml#parameter":
                    {
                        return "TimedText.Parameter.";
                    }
                case "http://www.w3.org/ns/ttml/profile":
                    {
                        return "TimedText.Profile.";
                    }
             }
            return "";
        }
        #endregion

        /// <summary>
        /// Get recorded metadata for the given attribute
        /// </summary>
        /// <param name="attribute">metadata attribute to retrieve</param>
        /// <returns>attribute value</returns>
        public string GetMetadata(string attribute)
        {
            if (this.Metadata.ContainsKey(attribute))
            {
                return (string)this.Metadata[attribute];
            }
            else
            {
                return ""; // 
            }
        }
    }
    #endregion

    #region Timed Text Root element
    /// <summary>
    /// The tt element serves as the root, document element of a document 
    /// instance. The tt element accepts as its children zero or one head 
    /// element followed by zero or one body element.
    /// </summary>
    public class TtElement : TimedTextElementBase
    {
        #region Private variables
        HeadElement m_head;
        BodyElement m_body;
        #endregion

        #region map ID's to regions.

        private Dictionary<string, RegionElement> m_regions;

        public Dictionary<string, RegionElement> Regions
        {
            get
            {
                return m_regions;
            }
        }

        #endregion

        #region map ID's to agents.
        private Dictionary<string, Metadata.AgentElement> m_agents;
        public Dictionary<string, Metadata.AgentElement> Agents
        {
            get
            {
                return m_agents;
            }
        }
        #endregion

        #region map ID's to style. for referential styles
        private Dictionary<string, StyleElement> m_styles;
        public Dictionary<string, StyleElement> Styles
        {
            get
            {
                return m_styles;
            }
        }
        #endregion

        #region map parameter values
        private Dictionary<string, string> m_parameters;
        public Dictionary<string, string> Parameters
        {
            get
            {
                return m_parameters;
            }
        }
        #endregion

        #region Constructor
        public TtElement()
        {
            m_parameters = new Dictionary<string, string>();
            m_styles = new Dictionary<string, StyleElement>();
            m_agents = new Dictionary<string, Metadata.AgentElement>();
            m_regions = new Dictionary<string, RegionElement>();
        }
        #endregion

        #region Formatting
        /// <summary>
        /// return the root formatting object
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {
            // if there is no body. then empty regions would be pruned
            // see 9.3.3.  part 5. map each non-empty region element to 
            // an fo:block-container element...
            if (m_body == null) return null;
            if (!m_body.TemporallyActive(tick)) return null;

            #region create single root and flow for the document.
            var root = new Formatting.Root(this);
            var flow = new Formatting.Flow(null);
            flow.Parent = root;
            root.Children.Add(flow);
            #endregion

            #region add a block container to the flow for each temporally active region
            foreach (var region in Regions.Values)
            {
                if (region.TemporallyActive(tick))
                {
                    var blockContainer = region.GetFormattingObject(tick);
                    #region apply animations on regions
                    foreach (var child in region.Children)
                    {
                        if (child is SetElement)
                        {
                             var fo = ((child as SetElement).GetFormattingObject(tick));
                            if (fo is Animation)
                            {
                                blockContainer.Animations.Add(fo as Animation);
                            }
                        }
                    }
                    #endregion
 
                    blockContainer.Parent = flow;
                    flow.Children.Add(blockContainer);

                    #region create a new subtree for the body element 
                    /// select it into this region by adding its children 
                    /// to block container
                    var block = m_body.GetFormattingObject(tick);
                    if (block != null)
                    {
                        block.Prune(region.Id);  // deselect any content not for this region
                        if (block.Children.Count > 0)
                        {
                            if (block.Children[0].Children.Count > 0)
                            {
                                blockContainer.Children.Add(block);
                                block.Parent = blockContainer;
                            }
                        }
                    }
                    #endregion
                }
            }
            #endregion
            return root;
        }
        #endregion

        #region validity
        /*
         <tt
            tts:extent = string
            xml:id = ID
            xml:lang = string     (required)
            xml:space = (default|preserve) : default
            {any attribute in TT Parameter namespace ...}
            {any attribute not in default or any TT namespace ...}>
            Content: head?, body?
        </tt>
         */

        /// <summary>
        /// Check tt element attribute validity
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(true, false, false, false, false, false);

            if (Language == null)
            {
                Error("TT element must specify xml:lang attribute ");
            }
        }

        /// <summary>
        /// Check tt element validity
        /// </summary>
        protected override void ValidElements()
        {
            bool isValid = true;
            // we need an extra check to validate the root attributes in order
            // to ensure parameters are parsed.
            ValidAttributes();   
 
            #region check this elements model
            switch (Children.Count)
            {
                case 0: 
                    return;
                case 1:
                    {
                        #region test if child element is head or body
                        if (Children[0] is HeadElement)
                        {
                            m_head = Children[0] as HeadElement;
                            isValid = true;
                        }
                        else if (Children[0] is BodyElement)
                        {
                            m_body = Children[0] as BodyElement;
                            m_head = new HeadElement();
                            Children.Clear();
                            Children.Add(m_head);
                            Children.Add(m_body);
                            isValid = true;
                        }
                        else
                        {
                            isValid = false;
                        }
                        #endregion
                    }
                    break;
                case 2:
                    {
                        #region Check first child is head, and second is body
                        if (Children[0] is HeadElement)
                        {
                            m_head = Children[0] as HeadElement;
                        }
                        if (Children[1] is BodyElement)
                        {
                            m_body = Children[1] as BodyElement;
                        }
                        else
                        {
                            isValid = (m_body != null && m_head != null);
                        }
                        #endregion
                    }
                    break;
                default:
                    {
                        #region Cannot be valid
                        isValid = false;
                        #endregion
                    }
                    break;
            }
            #endregion
 
            if (!isValid)
            {
                Error("erroneous child in " + this.ToString());
            }

            #region now check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion

            #region Add default region if none was specified
            if (isValid && Regions.Count < 1)
            {  
                LayoutElement defaultLayout = new LayoutElement();
                defaultLayout.LocalName = "layout";
                defaultLayout.Namespace = m_head.Namespace;

                m_head.Children.Add(defaultLayout);
                defaultLayout.Parent = m_head;
                RegionElement defaultRegion = new RegionElement();
                defaultLayout.Children.Add(defaultRegion);
                defaultRegion.Parent = defaultLayout;
                defaultRegion.Id = RegionElement.DefaultRegionName;
                Root.Regions[defaultRegion.Id] = defaultRegion;
            }
#endregion

        }
        #endregion

    }
    #endregion

    #region Timed Text Head element
    /// <summary>
    /// The head element is a container element used to group header matter,
    /// including metadata, styling, and layout information.
    /// The head element accepts as its children zero or more elements in the 
    /// Metadata.class element group, followed by zero or one styling element, 
    /// followed by zero or one layout element.
    /// </summary>
    public class HeadElement : TimedTextElementBase
    {

        #region validity
        /*
         <head
            xml:id = ID
            xml:lang = string
            xml:space = (default|preserve)>
            Content: Metadata.class*,  Parameters.class*, styling?, layout?
         * 
         * what does lang mean here? 
         * why are foreign and metadata attributes not allowed?
         </head>
         */
        /// <summary>
        /// Check the attributes of the timed text head element
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, false, false, false, false, false);
        }

        /// <summary>
        /// Check the validity of the timed text head element
        /// </summary>
        protected override void ValidElements()
        {
            bool isValid = true;
            int child = 0;

            #region allow artibtrary metadata
            while ((child < Children.Count)
                && ((Children[child] is MetadataElement)
                || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow an arbitrary number of profile elements
            while ((child < Children.Count)
                && ((Children[child] is Parameter.ProfileElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow an optional styling and optional layout element
            if (child < Children.Count)
            {
                if (Children[child] is StylingElement)
                {
                    if (Children.Count == (child + 1))
                    {
                        isValid = true;
                    }
                    else
                    {
                        isValid = (Children[child + 1] is LayoutElement) && (Children.Count == (child + 2));
                    }
                }
                else if (Children[child] is LayoutElement)
                {
                    isValid = (Children.Count == (child + 1));
                }
                else
                {
                    Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
                }
            }
            #endregion

            if (!isValid)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text Body element
    /// <summary>
    /// The body element functions as a logical container and a temporal structuring 
    /// element for a sequence of textual content units represented as logical divisions.
    /// The body element accepts as its children zero or more elements in the 
    /// Metadata.class element group, followed by zero or more elements in the Animation.
    /// class element group, followed by zero or more div elements.
    /// </summary>
    public class BodyElement : TimedTextElementBase
    {
        #region Constructor
        /// <summary>
        /// Constructor to set defaults
        /// </summary>
        public BodyElement()
        {
            TimeSemantics = TimeContainer.Par;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Get formatting object for body element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {

            Block block = null;

            if (TemporallyActive(tick))
            {
                block = new Block(this);

                foreach (var child in Children)
                {
                    if (child is DivElement)
                    {
                        var fo = (child as DivElement).GetFormattingObject(tick);
                        if (fo != null)
                        {
                            fo.Parent = block;
                            block.Children.Add(fo);
                        }
                    }

                    if (child is SetElement)
                    {
                        var fo = ((child as SetElement).GetFormattingObject(tick)) as Animation;
                        if (fo != null)
                        {
                            // fo.Parent = block;
                            block.Animations.Add(fo);
                        }
                    }
                }
            }
            return block;
        }
        #endregion

        #region Validity
        /*
        <body
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          region = IDREF
          style = IDREFS
          timeContainer = (par|seq)
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*, div*
        </body>
        */

        /// <summary>
        /// Validate body element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, true, true, true);
        }

        /// <summary>
        /// Validate body element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set elements (Animation class)
            while ((child < Children.Count)
               && ((Children[child] is SetElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary number of div elements
            while ((child < Children.Count)
               && ((Children[child] is DivElement) 
                // || (Children[child] is anonymousSpan)
            ))
            {
                child++;
            }
            #endregion

            #region Ensure no other children
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text Div element
    /// <summary>
    /// The div element functions as a logical container and a temporal structuring 
    /// element for a sequence of textual content units represented as logical paragraphs.
    /// The div element accepts as its children zero or more elements in the Metadata.class
    /// element group, followed by zero or more elements in the Animation.class element
    /// group, followed by zero or more p elements.
    /// </summary>
    public class DivElement : TimedTextElementBase
    {

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public DivElement()
        {
            TimeSemantics = TimeContainer.Par;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Get the formatting object for this element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {

            if (TemporallyActive(tick))
            {
                var block = new Block(this);

                foreach (var child in Children)
                {
                    if (child is DivElement || child is PElement)
                    {
                        var fo = (child as TimedTextElementBase).GetFormattingObject(tick);
                        if (fo != null)
                        {
                            fo.Parent = block;
                            block.Children.Add(fo);
                        }
                    }
                    if (child is SetElement)
                    {
                        var fo = ((child as SetElement).GetFormattingObject(tick)) as Animation;
                        if (fo != null)
                        {
                            block.Animations.Add(fo);
                        }
                    }
                }
                return block;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region Validity
        /*
        <div
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          region = IDREF
          style = IDREFS
          timeContainer = (par|seq)
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*, Block.class*
        </div>
        */
        /// <summary>
        /// Check validity of div element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, true, true, true);
        }

        /// <summary>
        /// Check validity of dive element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set elements (Animation class)
            while ((child < Children.Count)
               && ((Children[child] is SetElement)
            ))
            {
                child++;
            }
            #endregion

            #region allow arbitrary div, p elements (Block class)
            while ((child < Children.Count)
                && ((Children[child] is DivElement)
                || (Children[child] is PElement)
               //|| (Children[child] is anonymousSpan)
            ))                   
            {
                child++;
            }
            #endregion

            #region Ensure no other elements present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text P element
    /// <summary>
    /// A p element represents a logical paragraph, serving as a transition between 
    /// block level and inline level formatting semantics.The p element accepts as its
    /// children zero or more elements in the Metadata.class element group, followed by 
    /// zero or more elements in the Animation.class element group, followed by zero or 
    /// more span element, br element, or text nodes interpreted as anonymous spans.
    /// </summary>
    public class PElement : TimedTextElementBase
    {

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public PElement()
        {
            TimeSemantics = TimeContainer.Par;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Return formatting object for p element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {

            if (TemporallyActive(tick))
            {

                var block = new Paragraph(this);

                foreach (var child in Children)
                {
                    if (child is BrElement || child is AnonymousSpanElement)
                    {
                        var fo = (child as TimedTextElementBase).GetFormattingObject(tick);
                        if (fo != null)
                        {
                            fo.Parent = block;
                            block.Children.Add(fo);
                            foreach (var d in this.Metadata)
                            {
                                if (!child.Metadata.ContainsKey(d.Key))
                                {
                                    child.Metadata.Add(d.Key, d.Value);
                                }
                            }
                        }
                    }
                    else if (child is SpanElement)
                    {
                        var fo = (child as SpanElement).GetFormattingObject(tick);
                        if (fo != null)
                        {
                            /// Flattened nested <span>A<span>B</span>C</span>
                            /// -> <Inline>A</Inline><Inline>B</Inline><Inline>C</Inline>
                            /// by hoisting out to outer context.
                            /// nested elements will still inherit correctly, as style is inherited 
                            /// throughthe tt_element tree, not the formatting object tree.
                            /// something to watch out for when computing relative values though.
                            foreach (var nestedInline in fo.Children)
                            {
                                nestedInline.Parent = block;
                                block.Children.Add(nestedInline);
                                // copy the childs animations into the grandchild
                                // - need to watch the order here.
                                // deepest animations should win, so they need to be last in the list. 
                                // we reverse the list on entry so they get inserted at the front in
                                // the right order.
                                fo.Animations.Reverse();  // these are getting discarded.
                                foreach (var animation in fo.Animations)
                                {
                                    Formatting.FormattingObject grandchild = nestedInline as Formatting.FormattingObject;
                                    grandchild.Animations.Insert(0, animation);
                                }
                            }

                        }
                    }
                    if (child is SetElement)
                    {
                        var fo = ((child as SetElement).GetFormattingObject(tick)) as Animation;
                        if (fo != null)
                        {
                            block.Animations.Add(fo);
                        }
                    }
                }
                return block;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region Validity
        /*
        <p
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          region = IDREF
          style = IDREFS
          timeContainer = (par|seq)
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*, Inline.class*
        </p>
        */
       
        /// <summary>
        /// Check validity of p element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, true, true, true);
        }

        /// <summary>
        /// Check validity of p element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
                 //      || (Children[child] is anonymousSpan)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set elements (Animation class)
            while ((child < Children.Count)
               && ((Children[child] is SetElement)
               //|| (Children[child] is anonymousSpan)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary span, br, PCDATA (Inline class)
            while ((child < Children.Count)
               && ((Children[child] is SpanElement)
               || (Children[child] is BrElement)
               || (Children[child] is AnonymousSpanElement)
            ))
            {
                child++;
            }
            #endregion

            #region Check no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region now check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion

        }
        #endregion
    }
    #endregion

    #region Timed Text Span element
    /// <summary>
    /// The span element functions as a logical container and a temporal
    /// structuring element for a sequence of textual content units having inline 
    /// level formatting semantics. The span element accepts as its children zero or 
    /// more elements in the Metadata.class element group, followed by zero or more 
    /// elements in the Animation.class element group, followed by zero or more br 
    /// element or text nodes interpreted as anonymous spans.
    /// 
    /// The span class also represents open text nodes, and thus has a Text property.
    /// When presented on a visual medium, a span element is intended to generate a 
    /// sequence of inline areas, each containing one or more glyph areas.
    /// </summary>
    public class SpanElement : TimedTextElementBase
    {

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public SpanElement(string text)
        {
            AnonymousSpanElement aSpan = new AnonymousSpanElement(text);
            Children.Add(aSpan);
            aSpan.Parent = this;
            TimeSemantics = TimeContainer.Par;
        }
 
        /// <summary>
        /// Constructor to set defaults
        /// </summary>
        public SpanElement()
        {
            TimeSemantics = TimeContainer.Par;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Return formatting object for span element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {
            if (TemporallyActive(tick))
            {
                var block = new Inline(this);

                foreach (var child in Children)
                {
                     if (child is BrElement || child is AnonymousSpanElement)
                    {
                       #region Add text to the Inline formatting object
                        var fo = (child as TimedTextElementBase).GetFormattingObject(tick);
                        if (fo != null)
                        {
                            fo.Parent = block;
                            block.Children.Add(fo);
                        }
                        #region copy metadata across to inline, since we want to use this
                        foreach (var d in this.Metadata)
                        {
                            if(!child.Metadata.ContainsKey(d.Key)){
                                child.Metadata.Add(d.Key, d.Value);
                            }
                        }
                        #endregion
                       #endregion
                    }
                    else if (child is SpanElement)
                     {
                         #region flatten span hierarchy
                         var fo = (child as SpanElement).GetFormattingObject(tick);
                         if (fo != null)
                         {
                             /*
                             /// Flattened nested <span>A<span>B</span>C</span>
                             /// -> <Inline>A</Inline><Inline>B</Inline><Inline>C</Inline>
                             /// by hoisting out to outer context.
                             /// Hoisted elements will still inherit correctly, as style is inherited through
                             /// the Timed Text tree, not the formatting object tree.
                             /// something to watch out for when computing relative 
                             /// values though.
                              */
                             foreach (var nestedInline in fo.Children)
                             {
                                 nestedInline.Parent = block;
                                 block.Children.Add(nestedInline);
                             }
                         }
                         #endregion
                     }
                    if (child is SetElement)
                    {
                        #region Add animations to Inline
                        var fo = ((child as SetElement).GetFormattingObject(tick)) as Animation;
                        if (fo != null)
                        {
                            block.Animations.Add(fo);
                        }
                        #endregion
                    }
                }
                return block;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region Validity
        /*
         <span
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          region = IDREF
          style = IDREFS
          timeContainer = (par|seq)
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*, Inline.class*
        </span>
        */
        /// <summary>
        /// Check validity of span element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, true, true, true);

        }

        /// <summary>
        /// Check valisdity of span element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
               //|| (Children[child] is anonymousSpan)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set elements (Animation class)
            while ((child < Children.Count)
               && ((Children[child] is SetElement)
               //|| (Children[child] is anonymousSpan)
             ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary span, br and PCDATA (Inline class)
            while ((child < Children.Count)
               && ((Children[child] is SpanElement)
               || (Children[child] is BrElement)
               || (Children[child] is AnonymousSpanElement)
            ))
            {
                child++;
            }
            #endregion

            #region Ensure no other element present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text Anonymous span element
    /// <summary>
    /// These are called out as a subclass of span, 
    /// as their validity is different than pure spans. 
    /// But generally they behave like spans.
    /// </summary>
    public class AnonymousSpanElement : SpanElement
    {
        #region Text property
        private string m_text = "";
        /// <summary>
        /// The PCDATA text of the span
        /// </summary>
        public string Text
        {
            get { return m_text; }
            set { m_text = value; }
        }
        #endregion

        #region Bidi order property
        /// <summary>
        /// Reordered version of the text applying the bidi rules
        /// </summary>
        //public System.Collections.ObjectModel.Collection<char> BidiVisualOrder
        //{
        //    get;
        //    set;
        //}
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public AnonymousSpanElement(string text)
        {
            m_text = text;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Return the formatting object for anonymous span element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {
            if (TemporallyActive(tick))
            {
                return new InlineContent(this);
            }
            return null;
        }
        #endregion

        public override void WriteElement(XmlWriter writer)
        {
            writer.WriteString(this.Text);
        }

    }

    #endregion

    #region Timed Text br element
    /// <summary>
    /// The br element denotes an explicit line break. This is a specialised
    /// form of span containing a Unicode break character
    /// </summary>
    public class BrElement : AnonymousSpanElement
    {
        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public BrElement()
            : base("\n")
        {
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Return formatting object for br element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {
            if (TemporallyActive(tick))
            {
                return new InlineContent(this);
            }
            return null;
        }
        #endregion

        #region validity
        /*
         <br
          style = IDREFS
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*
        </br>
        */

        /// <summary>
        /// Check valisity of br element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, false, false, false);
        }

        /// <summary>
        /// chack validity of br element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set element (Animation class)
            while ((child < Children.Count)
               && (Children[child] is SetElement))
            {
                child++;
            }
            #endregion

            #region Ensure no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text styling element
    /// <summary>
    /// The styling element is a container element used to group styling matter, 
    /// including metadata that applies to styling matter.The styling element accepts 
    /// as its children zero or more elements in the Metadata.class element group, 
    /// followed by zero or more style elements.
    /// </summary>
    public class StylingElement : TimedTextElementBase
    {
        #region validity
        /*
       <styling
              xml:id = ID
              xml:lang = string
              xml:space = (default|preserve)
              {any attribute not in default or any TT namespace ...}>
              Content: Metadata.class*, style*
        </styling>
        */
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, false, false, false, false, false);
        }

        protected override void ValidElements()
        {
            int child = 0;

            while ((child < Children.Count)
                && ((Children[child] is MetadataElement)
                || (Children[child] is Metadata.MetadataElement)))
            {
                child++;
            }
            while ((child < Children.Count)
                && (Children[child] is StyleElement))
            {
                child++;
            }

            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }

            // now check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
        }
        #endregion
    }
    #endregion

    #region Timed Text style element
    /// <summary>
    /// The style element is used to define a single style specification or a set 
    /// of style specifications.The style element accepts as its children zero or 
    /// more metadata elements.
    /// </summary>
    public class StyleElement : TimedTextElementBase
    {
        #region Validity
        /*
            <style
              style = IDREFS
              xml:id = ID
              xml:lang = string
              xml:space = (default|preserve)
              {any attribute in TT Style namespace ...}
              {any attribute in TT Style Extension namespace ...}
              {any attribute not in default or any TT namespace ...}>
              Content: Metadata.class*
            </style>
                     */
        /// <summary>
        /// Check valisity of style element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, false, true, false, false, false);
        }

        /// <summary>
        /// Check valisity of style element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Ensure no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text layout element
    /// <summary>
    /// The layout element is a container element used to group layout matter, including
    /// metadata that applies to layout matter.The layout element accepts as its children
    /// zero or more elements in the Metadata.class element group, followed by zero or more
    /// region elements.
    /// </summary>
    public class LayoutElement : TimedTextElementBase
    {
        #region Validity
        /*
         <layout
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, region*
        </layout>
        */
        /// <summary>
        /// Check validity of layout element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, false, false, false, false, false);
        }

        /// <summary>
        /// Check validity of layout element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count) && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary region elements
            while ((child < Children.Count) && (Children[child] is RegionElement))
            {
                child++;
            }
            #endregion

            #region Ensure no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text region element
    /// <summary>
    /// The region element is used to define a space or area into which content is to 
    /// be flowed for the purpose of presentation.The region element accepts as its 
    /// children zero or more elements in the Metadata.class element group, followed 
    /// by zero or more elements in the Animation.class element group, followed by zero 
    /// or more style elements.
    /// </summary>
    public class RegionElement : TimedTextElementBase
    {
        #region Constants
        /// <summary>
        /// Name for region if none is specified
        /// </summary>
        public static string DefaultRegionName
        {
            get
            {   // return a key that is not a legal XML ID.
                return "default region";
            }       
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public RegionElement()
        {
            TimeSemantics = TimeContainer.Par;
        }
        #endregion

        #region Formatting
        /// <summary>
        /// Return formatting object for region element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {
            if (TemporallyActive(tick))
            {
                return new BlockContainer(this);
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region Validity
        /*
         <region
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          style = IDREFS
          timeContainer = (par|seq)
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Style namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*, Animation.class*, style*
        </region>
        */
 
        /// <summary>
        /// Check validity of region element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, true, true, false, true);
        }

        /// <summary>
        /// Check vlidity of region element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary set element (Animation class)
            while ((child < Children.Count)
                && (Children[child] is SetElement))
            {
                child++;
            }
            #endregion

            #region Allow arbitrary style elements
            while ((child < Children.Count)
               && (Children[child] is StyleElement)
            )
            {
                StyleElement s = Children[child] as StyleElement;
                #region copy nested style attributes over as if they were inline
                foreach (var a in s.Attributes)
                {
                    // we should really check if its already defined, however
                    // by adding at the start we ensure the later (inline)
                    // style will override.
                    this.Attributes.Insert(0, a);
                }
                #endregion
                child++;
            }
            #endregion

            #region Ensure no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion

        }
        #endregion

    }
    #endregion

    #region Timed Text set element
    /// <summary>
    /// The set element is used as a child element of a content element in order to 
    /// express a discrete change of some style parameter value that applies over some 
    /// time interval.The set element accepts as its children zero or more elements in 
    /// the Metadata.class element group.
    /// </summary>
    public class SetElement : TimedTextElementBase
    {
        #region Formatting
        /// <summary>
        /// Return the formatting object for set element
        /// </summary>
        /// <param name="regionId"></param>
        /// <param name="tick"></param>
        /// <returns></returns>
        public override FormattingObject GetFormattingObject(TimeCode tick)
        {

            if (TemporallyActive(tick))
            {
                var animation = new Animation(this);
                return animation;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region Validity
        /*
        <set
          begin = <timeExpression>
          dur = <timeExpression>
          end = <timeExpression>
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {a single attribute in TT Style or TT Style Extension namespace}
          {any attribute not in default or any TT namespace ...}>
          Content: Metadata.class*
        </set>
        */
        /// <summary>
        /// Check validity of set element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, false, true, true, false, false);
        }

        /// <summary>
        /// Check validity of set element content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Allow arbitrary metadata
            while ((child < Children.Count)
               && ((Children[child] is MetadataElement)
               || (Children[child] is Metadata.MetadataElement)
            ))
            {
                child++;
            }
            #endregion

            #region Ensure no other element is present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            #region Check each of the children is individually valid
            foreach (TimedTextElementBase element in Children)
            {
                element.Valid();
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Timed Text metadata element
    public class MetadataElement : TimedTextElementBase
    {
        #region Validity
        /*
        <metadata
          xml:id = ID
          xml:lang = string
          xml:space = (default|preserve)
          {any attribute in TT Metadata namespace ...}
          {any attribute not in default or any TT namespace ...}>
          Content: {any element not in TT namespace}*
        </metadata>
        */
        /// <summary>
        /// Check validity of metadata element attributes
        /// </summary>
        protected override void ValidAttributes()
        {
            ValidateAttributes(false, true, false, false, false, false);
        }

        /// <summary>
        /// Check validity of metadata content model
        /// </summary>
        protected override void ValidElements()
        {
            int child = 0;

            #region Ensure the children are not tt elements.
            while ((child < Children.Count) &&
                    ( 
                      Children[child] is Metadata.MetadataElement ||
                     !(Children[child] is TimedTextElementBase))
                    )
            {
                child++;
            }
            #endregion

            #region Ensure no other elements are present
            if (Children.Count != child)
            {
                Error(Children[child].ToString() + " is not allowed in " + this.ToString() + " at position " + child.ToString(CultureInfo.CurrentCulture));
            }
            #endregion

            // can't check the validity of other children as they are not TT
        }
        #endregion
    }
    #endregion
}

